package org.dromara.common.web.filter;

import cn.hutool.core.io.IoUtil;
import org.dromara.common.core.constant.Constants;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;


/**
 * @Author 刘武贵
 * @Date 2024/8/15 22:00
 * @Description 功能描述：构建可重复读取inputStream的request
 */
/**
 * RepeatedlyRequestWrapper 类继承自 HttpServletRequestWrapper，用于包装 HTTP 请求，
 * 使其能够处理重复请求. 该类主要目的是在处理 HTTP 请求时，提供一种机制，
 * 以便在需要时可以重新处理相同的请求，这对于需要重试请求的场景特别有用。
 */
public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper {

    /**
     * 私有最终成员变量body，用于存储请求或响应的具体内容
     * 该变量被声明为final，意味着一旦在对象创建时被赋值，就不能再被修改
     * 使用byte数组的形式存储，是为了能够灵活处理各种类型的数据，
     * 因为请求或响应的内容可以是文本、二进制数据等，使用byte数组可以不受数据类型限制
     */
    private final byte[] body;

    /**
     * 构造函数用于初始化RepeatedlyRequestWrapper对象
     * 它通过对HttpServletRequest进行包装，以方便多次读取请求体
     *
     * @param request  原始的HttpServletRequest对象，用于初始化本对象
     * @param response ServletResponse对象，虽然在构造函数中未直接使用，
     *                 但可以用于将来设置响应字符编码
     * @throws IOException 如果读取请求体时发生输入输出错误
     */
    public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException {
        super(request);
        // 设置请求和响应的字符编码为UTF-8，确保字符的正确解析
        request.setCharacterEncoding(Constants.UTF8);
        response.setCharacterEncoding(Constants.UTF8);

        // 读取请求体的字节内容，存储在body变量中，以便后续处理或多次读取
        body = IoUtil.readBytes(request.getInputStream(), false);
    }

    /**
     * 返回一个BufferedReader，用于从输入流中按行读取数据
     *
     * @return BufferedReader 用于按行读取输入流的数据
     * @throws IOException 如果在获取输入流过程中发生I/O错误
     *
     * 为什么这样转换：将输入流包装成BufferedReader是为了能够按行读取文本行，
     * 而InputStreamReader用于将字节流转换为字符流，以便于处理文本数据。
     */
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    /**
     * 重写getInputStream方法，返回一个ServletInputStream对象
     * 该方法主要用于处理HTTP请求的输入流
     *
     * @return 返回一个自定义的ServletInputStream对象，该对象封装了请求的主体内容
     * @throws IOException 如果读取输入流发生错误，则抛出IOException异常
     */
    @Override
    public ServletInputStream getInputStream() throws IOException {
        // 将请求的主体内容转换为ByteArrayInputStream，以便后续读取
        final ByteArrayInputStream bais = new ByteArrayInputStream(body);
        // 返回一个匿名的ServletInputStream类实例，用于处理HTTP请求的输入
        return new ServletInputStream() {

            /**
             * 从ByteArrayInputStream中读取下一个字节
             */
            @Override
            public int read() throws IOException {
                return bais.read();
            }

            /**
             * 返回ByteArrayInputStream中可用字节的数量
             */
            @Override
            public int available() throws IOException {
                return body.length;
            }

            /**
             * 返回是否所有数据都已经被读取完毕
             * 此处假设数据读取未完成，因此返回false
             */
            @Override
            public boolean isFinished() {
                return false;
            }

            /**
             * 返回数据是否准备好可被读取
             * 此处假设数据未准备好，因此返回false
             */
            @Override
            public boolean isReady() {
                return false;
            }

            /**
             * 设置一个读取监听器，用于异步读取操作
             */
            @Override
            public void setReadListener(ReadListener readListener) {

            }
        };
    }
}
